﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace Objects.Transactions
{
    /// <summary>
    /// Потокозависимая транзакция
    /// </summary>
    public class Transaction : IDisposable
    {
        private static int _trnCounter = 0;
        protected static int GetTrnCounter()
        {
            return ++_trnCounter;
        }
        protected static string GetDefaultTrnName()
        {
            return "Transaction-{0}".goFormat(GetTrnCounter());
        }
        private TransactionableRegistry reg;
        private TransactionState _State;
        /// <summary>
        /// Состояние транзакции
        /// </summary>
        public TransactionState State
        {
            get { return _State; }
            set
            {
                if (_State != value)
                    switch (_State)
                    {
                        case TransactionState.Inited:
                            if (value == TransactionState.Commited)
                                Commit();
                            else if (value == TransactionState.Aborted)
                                Abort();
                            else
                                throw new TransactionException("Попытка перевести транзакцию [{0}] в неопределенное состояние {1}".goFormat(Name, value));
                            break;
                        case TransactionState.Commited:
                            throw new TransactionException("Попытка смены состояния закрепленной транзакции [{0}].".goFormat(Name));
                        case TransactionState.Aborted:
                            throw new TransactionException("Попытка смены состояния прерванной транзакции [{0}].".goFormat(Name));
                        default:
                            throw new TransactionException("Транзакция [{0}] находится в неопределенном состоянии.".goFormat(Name));
                    }

            }
        }
        private List<Transaction> slaveTrn;

        private string _Name;
        /// <summary>
        /// Внутреннее имя
        /// </summary>
        public string Name
        {
            get { return _Name; }
            set { _Name = value; }
        }
        private string _ShowName;
        /// <summary>
        /// Отображаемое имя
        /// </summary>
        public string ShowName
        {
            get
            {
                if (_ShowName.IsEmpty())
                    return Name;
                else
                    return _ShowName;
            }
            set { _ShowName = value; }
        }

        #region Конструкторы
        public Transaction() : this(null, null) { }

        public Transaction(string Name) : this(Name, null) { }

        public Transaction(string Name, string ShowName)
        {
            if (Name.IsEmpty())
                this.Name = GetDefaultTrnName();
            else
            {
                GetTrnCounter();
                this.Name = Name;
            }
            this.ShowName = ShowName;
            slaveTrn = new List<Transaction>();
            reg = TransactionableRegistry.Instance;
            reg.AddTransaction(this);
            _State = TransactionState.Inited;
        }
        #endregion

        private int pntCounter;

        #region Основные операции
        /// <summary>
        /// Создать точку восстановления с именем по-умолчанию
        /// </summary>
        /// <param name="namePoint">Наименование точки (чувствительно к регистру)</param>
        public void CreatePoint()
        {
            CreatePoint("Point-{0}".goFormat(++pntCounter), null);
        }

        /// <summary>
        /// Создать точку восстановления
        /// </summary>
        /// <param name="namePoint">Наименование точки (чувствительно к регистру)</param>
        public void CreatePoint(string namePoint)
        {
            CreatePoint(namePoint, null);
        }

        /// <summary>
        /// Создать точку восстановления
        /// </summary>
        /// <param name="namePoint">Наименование точки (чувствительно к регистру)</param>
        /// <param name="showName">Отображаемое имя</param>
        public void CreatePoint(string namePoint, string showName)
        {
            slaveTrn.Add(new Transaction(namePoint, showName));
        }

        /// <summary>
        /// Безвозвратно закрепить транзакцию
        /// </summary>
        public void Commit()
        {
            reg.CommitTrn(this);
            _State = TransactionState.Commited;
        }

        /// <summary>
        /// Безвозратно откатить транзакцию
        /// </summary>
        public void Abort()
        {
            reg.RollbackTrn(this);
            _State = TransactionState.Aborted;
        }

        /// <summary>
        /// Откатить к началу транзакции (сбросить все операции)
        /// </summary>
        public void RollbackToBegin()
        {
            reg.RollbackTrn(this);
            reg.AddTransaction(this);
        }

        /// <summary>
        /// Откатить последнюю операцию (до точки сохранения или к началу транзакции)
        /// </summary>
        public void RollbackLast()
        {
            int cnt = slaveTrn.Count - 1;
            if (cnt < 0)
                RollbackToBegin();
            else
            {
                Transaction t = slaveTrn[cnt];
                slaveTrn.RemoveAt(cnt);
                reg.RollbackTrn(t);
            }
        }

        /// <summary>
        /// Откатить транзакцию к заданной точке
        /// </summary>
        /// <param name="point">Наименование точки (чувствительно к регистру букв)</param>
        /// <exception cref="TransactionException">Если точка отсутствует</exception>
        public void RollbackTo(string point)
        {
            var slTrn = slaveTrn.LastOrDefault(s => s.Name == point);
            if (slTrn == null)
                throw new TransactionException("Не найдена точка {0}".goFormat(point));
            else
            {
                reg.RollbackTrn(slTrn);
            }
        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (State == TransactionState.Inited)
                State = TransactionState.Aborted;
        }

        #endregion

        public override string ToString()
        {
            if (ShowName.IsEmpty())
                return base.ToString();
            else
                return ShowName;
        }
    }

    public class TransactionException : Exception
    {
        public TransactionException() : base() { }
        public TransactionException(string message) : base(message) { }
        public TransactionException(string message, Exception innerException) : base(message, innerException) { }
        public TransactionException(SerializationInfo info, StreamingContext context) : base(info, context) { }
    }

    public enum TransactionState
    {
        /// <summary>
        /// Инициирована
        /// </summary>
        Inited,
        /// <summary>
        /// Закреплена
        /// </summary>
        Commited,
        /// <summary>
        /// Отклонена
        /// </summary>
        Aborted
    }
}
